/* EvolutionAlgorithm.h
 *
 *	Copyright (C) 2008 CRIMERE
 *	Copyright (C) 2008 Jean-Marc Mercier
 *	
 *	This file is part of OTS (optimally transported schemes), an open-source library
 *	dedicated to scientific computing. http://code.google.com/p/optimally-transported-schemes/
 *
 *	CRIMERE makes no representations about the suitability of this
 *	software for any purpose. It is provided "as is" without express or
 *	implied warranty.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */

#include <src/miscellaneous/OS_basic.h>
#include <src/miscellaneous/pattern/Observable.h>
#include <map>
#if !defined(_EvolutionAlgorithm_)
#define _EvolutionAlgorithm_

/*! \brief Helper to handle time dependent evolution problem.
	
	This class is an helper to define time dependent algorithms. 
	It embeds a single step algorithm (template class Algorithm).
*/
		template <class Algorithm>
		class EvolutionAlgorithm
		{
		public :
			typedef typename Algorithm														algorithm_type;
			typedef typename algorithm_type::time_type										time_type;
			typedef typename algorithm_type::result_type									result_type;

			typedef typename algorithm_type::time_operator_type								time_operator_type;
			typedef typename time_operator_type::state_variable_type						time_operator_state_variable_type;
			typedef typename time_operator_state_variable_type::value_type					time_operator_state_variable_type_value_type;

			typedef typename time_operator_state_variable_type_value_type					solution_type;


			typedef typename std::set<time_type>											set_time_type;
			typedef typename set_time_type::const_iterator									set_time_const_iterator_type;
			typedef typename set_time_type::iterator										set_time_iterator_type;
			typedef typename std::map<time_type,solution_type>								record_set_type;

			virtual ~EvolutionAlgorithm(){};
			EvolutionAlgorithm() : 	S_0_(new time_operator_state_variable_type_value_type)
			{
				single_step_algorithm_.get_operator().set_state_variable(S_0_);
				current_time_ = times_.end();
			};
//!			Accessor to the single step algorithm single_step_algorithm_.
			void set_algorithm(const Algorithm & from) {single_step_algorithm_ = from;};
			const Algorithm& get_algorithm() const {return single_step_algorithm_;};
			Algorithm& get_algorithm() {return single_step_algorithm_;};

//!			Accessor to the solution, used typically to insert the initial conditions.
			void set_solution(const result_type & S_0) {
				*S_0_ = S_0;
				single_step_algorithm_.set_state_variable(S_0_);
			};

//!			Accessor to the last computed solution.
			const solution_type & get_solution() const {
				return *S_0_;
			};
//!			Number of time steps used.
			OS_size get_iterations() const {
				OS_size return_ = times_.size()-2;
				return std::max(return_, (OS_size) 0);
			};
//!			accessor to the last computed time.
			time_type get_current_time() const {
				set_time_const_iterator_type end = times_.end();
				if (current_time_ != end)
						return *current_time_;
				else {
					end --;
					return *end;
				};
			};


//!			insert a list of discrete time at which the solution must be computed.
			template <class Iterator>
			const solution_type & operator() (Iterator begin, Iterator end) {
				            times_.insert(begin, end);
							return (*this)();
			};

//!			insert a single discrete time at which the solution must be computed.
			void insert_time(time_type time) {
				            times_.insert(time);
							current_time_ = times_.begin();
			};
//!			reset the time.
			void reset_time() {
				            times_.clear();
			};
//!			The full propagation from begin time to end time.
			const solution_type & operator()()
			{
				for (current_time_ = times_.begin(); current_time_ !=  times_.end(); ) {
					(*this)++;
				};
				return get_solution();
			};
//!			A one step propagation at current_time_.
			const solution_type & operator++()
			{
				if (current_time_ == times_.end()) return get_solution();
				set_time_const_iterator_type it_next = current_time_;
				++it_next;
				if (it_next != times_.end()) {
					time_type time_expected = *it_next;
					time_type real_time = time_expected;

					set_solution(single_step_algorithm_(*current_time_, real_time, get_solution() ));
					if (std::abs(time_expected - real_time) > THRESHOLD)
						times_.insert(real_time);
				}
				++current_time_;
				return get_solution();
			};
		private :
//!			the set of discrete times already attained.
			set_time_type times_;
//!			the current time.
			set_time_iterator_type					current_time_;
//!			the single step algorithm.
			Algorithm								single_step_algorithm_;
//!			the solution at each steps.
			time_operator_state_variable_type		S_0_;
		};
#endif